package com.world.rickcloudendpoint.algorithm.tracks;

import java.util.ArrayList;
import java.util.List;

/**
 * @description: 地理坐标处理工具类
 * @author: jlsong
 * @date: 2022/01/26 20:31:01
 * @version: 1.0
 */
public class GeographicalCoordinates {
    // 地球半径(这里取的是平均半径)
    private static final double EARTH_RADIUS = 6.371229 * 1e6;

    /**
     * 角度弧度计算公式 rad: ()
     * 360度 = 2 π π=Math.PI
     * x度 = x * π / 360 弧度
     *
     * @param degree
     * @return
     */
    private static double getRadian(double degree) {
        return degree * Math.PI / 180.0;
    }


    /**
     * 弧度换成度
     *
     * @param radian 弧度
     * @return
     */
    public static double getDegree(double radian) {
        return radian * 180 / Math.PI;
    }

    /**
     * 依据经纬度计算两点之间的距离
     *
     * @param lng1 地点A的经度
     * @param lat1 地点A的纬度
     * @param lng2 地点B的经度
     * @param lat2 地点B的纬度
     * @return double 单位:米
     */
    public static double getDistance(double lng1, double lat1, double lng2, double lat2) {
        double radLat1 = getRadian(lat1);
        double radLat2 = getRadian(lat2);
        double a = radLat1 - radLat2;// 两点纬度差
        double b = getRadian(lng1) - getRadian(lng2); // 两个点的经度差
        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) + Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
        s = s * EARTH_RADIUS;
        return s;
    }

    /**
     * 大地坐标系资料WGS-84
     */
    // 长半径
    private static final double a = 6378137;
    // 短半径
    private static final double b = 6356752.3142;
    // 扁角
    private static final double f = 1 / 298.2572236;

    /**
     * 通过一个点坐标计算另一个经纬度
     *
     * @param lon  经度
     * @param lat  纬度
     * @param brng 方位角(传入角度)
     * @param dist 距离(米)
     * @return double[] 经纬度
     */
    public static double[] computerThatLonLat(double lon, double lat, double brng, double dist) {
        double alpha1 = getRadian(brng);
        double sinAlpha1 = Math.sin(alpha1);
        double cosAlpha1 = Math.cos(alpha1);

        double tanU1 = (1 - f) * Math.tan(getRadian(lat));
        double cosU1 = 1 / Math.sqrt(1 + tanU1 * tanU1);
        double sinU1 = tanU1 * cosU1;
        double sigmal = Math.atan2(tanU1, cosAlpha1);
        double sinAlpha = cosU1 * sinAlpha1;
        double cosSqAlpha = 1 - sinAlpha * sinAlpha;
        double uSq = cosSqAlpha * (a * a - b * b) / (b * b);
        double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
        double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));

        double cos2SigmaM = 0;
        double sinSigma = 0;
        double cosSigma = 0;
        double sigma = dist / (b * A), sigmaP = 2 * Math.PI;
        while (Math.abs(sigma - sigmaP) > 1e-12) {
            cos2SigmaM = Math.cos(2 * sigmal + sigma);
            sinSigma = Math.sin(sigma);
            cosSigma = Math.cos(sigma);
            double deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)
                    - B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
            sigmaP = sigma;
            sigma = dist / (b * A) + deltaSigma;
        }

        double tmp = sinU1 * sinSigma - cosU1 * cosSigma * cosAlpha1;
        double lat2 = Math.atan2(sinU1 * cosSigma + cosU1 * sinSigma * cosAlpha1, (1 - f) * Math.sqrt(sinAlpha * sinAlpha + tmp * tmp));
        double lambda = Math.atan2(sinSigma * sinAlpha1, cosU1 * cosSigma - sinU1 * sinSigma * cosAlpha1);
        double C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));
        double L = lambda - (1 - C) * f * sinAlpha *
                (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
        // final bearing
        double revAz = Math.atan2(sinAlpha, -tmp);

        double[] coordinates = new double[2];
        coordinates[0] = lon + getDegree(L);
        coordinates[1] = getDegree(lat2);
        return coordinates;
    }

    /**
     * 传入两点，使用默认的宽度(10m)、间隔(10m)、高度200、长边生成航迹点
     *
     * @param lng1 地点A的经度
     * @param lat1 地点A的纬度
     * @param lng2 地点B的经度
     * @param lat2 地点B的纬度
     * @return
     */
    public static List<Coordinate> defaultGenerateCoordinates(double lng1, double lat1, double lng2, double lat2) {
        return coordinateProcessing(lng1, lat1, lng2, lat2, 10, 10, 200, true);
    }

    /**
     * 传入两点，使用自定义的宽度、间隔、高度、长边生成航迹点
     *
     * @param lng1      地点A的经度
     * @param lat1      地点A的纬度
     * @param lng2      地点B的经度
     * @param lat2      地点B的纬度
     * @param width     宽度，单位：m
     * @param interval  间隔距离，单位：m
     * @param high      航迹点高度，单位：m
     * @param direction true：长边；false：短边
     * @return
     */
    public static List<Coordinate> customGenerateCoordinates(double lng1, double lat1, double lng2, double lat2,
                                                             float width, float interval, float high, boolean direction) {
        return coordinateProcessing(lng1, lat1, lng2, lat2, width, interval, high, direction);
    }

    /**
     * 返回处理后的航迹点坐标的集合
     *
     * @param lng1      地点A的经度
     * @param lat1      地点A的纬度
     * @param lng2      地点B的经度
     * @param lat2      地点B的纬度
     * @param width     宽度，单位：m
     * @param interval  间隔距离，单位：m
     * @param high      航迹点高度，单位：m
     * @param direction true：长边；false：短边
     * @return
     */
    private static List<Coordinate> coordinateProcessing(double lng1, double lat1, double lng2, double lat2,
                                                         float width, float interval, float high, boolean direction) {
        // 两点间距离
        double distance = getDistance(lng1, lat1, lng2, lat2);
        // 计算偏移角
        double offsetAngle = Math.toDegrees(Math.atan((lng1 - lng2) / (lat1 - lat2)));
        // 计算单边的航迹点数量
        int divisionNum;
        List<Coordinate> oneCoordinateList;
        List<Coordinate> twoCoordinateList;
        List<Coordinate> coordinateList;

        Coordinate coordinate1, coordinate2;

        if (direction) {
            // 长边
            divisionNum = (int) Math.ceil(width * 2 / interval) + 1;
            // 上
            oneCoordinateList = new ArrayList<>(divisionNum);
            // 下
            twoCoordinateList = new ArrayList<>(divisionNum);
            coordinateList = new ArrayList<>(divisionNum * 2);

            int temp = divisionNum / 2;
            // 处理点左边
            for (int i = temp; i >= 0; i--) {
                coordinate1 = new Coordinate();
                coordinate2 = new Coordinate();
                // 上面左边
                double[] leftCoordinates = computerThatLonLat(lng2, lat2, -90 + offsetAngle, i * interval);
                coordinate1.setLon(leftCoordinates[0]);
                coordinate1.setLat(leftCoordinates[1]);
                coordinate1.setAlt(high);
                oneCoordinateList.add(coordinate1);
                // 下面左边
                double[] rightCoordinates = computerThatLonLat(lng1, lat1, -90 + offsetAngle, i * interval);
                coordinate2.setLon(rightCoordinates[0]);
                coordinate2.setLat(rightCoordinates[1]);
                coordinate2.setAlt(high);
                twoCoordinateList.add(coordinate2);
            }
            // 处理点右边
            for (int i = 0; i <= temp; i++) {
                coordinate1 = new Coordinate();
                coordinate2 = new Coordinate();
                // 上面右边
                double[] leftCoordinates = computerThatLonLat(lng2, lat2, 90 + offsetAngle, i * interval);
                coordinate1.setLon(leftCoordinates[0]);
                coordinate1.setLat(leftCoordinates[1]);
                coordinate1.setAlt(high);
                oneCoordinateList.add(coordinate1);
                // 下面右边
                double[] rightCoordinates = computerThatLonLat(lng1, lat1, 90 + offsetAngle, i * interval);
                coordinate2.setLon(rightCoordinates[0]);
                coordinate2.setLat(rightCoordinates[1]);
                coordinate2.setAlt(high);
                twoCoordinateList.add(coordinate2);
            }

            // 如果A点在上面，则交换两个集合的数据
            if (lat1 > lat2) {
                List<Coordinate> tempList = new ArrayList<>(oneCoordinateList);
                oneCoordinateList = new ArrayList<>(twoCoordinateList);
                twoCoordinateList = new ArrayList<>(tempList);
            }
            // 点排序
            boolean flag = false;
            for (int i = 0; i < oneCoordinateList.size(); i++) {
                if (flag) {
                    // 从下往上
                    coordinateList.add(twoCoordinateList.get(i));
                    coordinateList.add(oneCoordinateList.get(i));
                    flag = false;
                } else {
                    // 从上往下
                    coordinateList.add(oneCoordinateList.get(i));
                    coordinateList.add(twoCoordinateList.get(i));
                    flag = true;
                }
            }
        } else {
            // 短边
            divisionNum = (int) Math.ceil(distance / interval) + 1;
            oneCoordinateList = new ArrayList<>(divisionNum); // 保存左侧的所有点
            twoCoordinateList = new ArrayList<>(divisionNum); // 保存右侧的所有点
            coordinateList = new ArrayList<>(divisionNum * 2);

            // 计算第一个点左边的坐标
            double[] leftCoordinate = computerThatLonLat(lng1, lat1, -90 + offsetAngle, width);
            // 计算第一个点右边的坐标
            double[] rightCoordinate = computerThatLonLat(lng2, lat2, 90 + offsetAngle, width);
            // 判断那个点在上方
            if (lat1 < lat2) {
                // A下B上
                // 计算出所有点并且进行左右分类
                for (int i = 0; i < divisionNum; i++) {
                    coordinate1 = new Coordinate();
                    coordinate2 = new Coordinate();
                    // 左边
                    double[] leftCoordinates = computerThatLonLat(leftCoordinate[0], leftCoordinate[1], 0 + offsetAngle, i * interval);
                    coordinate1.setLon(leftCoordinates[0]);
                    coordinate1.setLat(leftCoordinates[1]);
                    coordinate1.setAlt(high);
                    oneCoordinateList.add(coordinate1);
                    // 右边
                    double[] rightCoordinates = computerThatLonLat(rightCoordinate[0], rightCoordinate[1], 0 + offsetAngle, i * interval);
                    coordinate2.setLon(rightCoordinates[0]);
                    coordinate2.setLat(rightCoordinates[1]);
                    coordinate2.setAlt(high);
                    twoCoordinateList.add(coordinate2);
                }
            } else {
                // A上B下
                for (int i = 0; i < divisionNum; i++) {
                    coordinate1 = new Coordinate();
                    coordinate2 = new Coordinate();
                    // 左边
                    double[] leftCoordinates = computerThatLonLat(leftCoordinate[0], leftCoordinate[1], 180 + offsetAngle, i * interval);
                    coordinate1.setLon(leftCoordinates[0]);
                    coordinate1.setLat(leftCoordinates[1]);
                    coordinate1.setAlt(high);
                    oneCoordinateList.add(coordinate1);
                    // 右边
                    double[] rightCoordinates = computerThatLonLat(rightCoordinate[0], rightCoordinate[1], 180 + offsetAngle, i * interval);
                    coordinate2.setLon(rightCoordinates[0]);
                    coordinate2.setLat(rightCoordinates[1]);
                    coordinate2.setAlt(high);
                    twoCoordinateList.add(coordinate2);
                }
            }
            // 点排序
            boolean flag = false;
            for (int i = 0; i < oneCoordinateList.size(); i++) {
                if (flag) {
                    //  从右往左
                    coordinateList.add(twoCoordinateList.get(i));
                    coordinateList.add(oneCoordinateList.get(i));
                    flag = false;
                } else {
                    // 从左往右
                    coordinateList.add(oneCoordinateList.get(i));
                    coordinateList.add(twoCoordinateList.get(i));
                    flag = true;
                }
            }
        }
        return coordinateList;
    }
}
